﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using EffiProz.Core.Lib;
using EffiProz.Core.Store;
using EffiProz.Core.Errors;
using EffiProz.Core.Results;
using EffiProz.Core.Navigators;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Lib.IO;

namespace EffiProz.Core.Persist
{
    public class LobManager
    {
        const String resourceFileName =
    "EffiProz.Core.Resources.lob-schema.sql";
        static String[] starters = new String[] { "/*" };

        //
        Database database;
        LobStore lobStore;
        Session sysLobSession;

        //
        //
        int lobBlockSize = 1024 * 32;
        int totalBlockLimitCount = 1024 * 1024 * 1024;

        //
        Statement _getLob;
        Statement getLobPart;
        Statement _deleteLob;
        Statement deleteLobPart;
        Statement divideLobPart;
        Statement createLob;
        Statement createLobPart;
        Statement setLobLength;
        Statement setLobUsage;
        Statement getNextLobId;

        // LOBS columns
        private static class LOBS
        {

            public const int BLOCK_ADDR = 0;
            public const int BLOCK_COUNT = 1;
            public const int BLOCK_OFFSET = 2;
            public const int LOB_ID = 3;
        }

        private static class ALLOC_BLOCKS
        {

            public const int BLOCK_COUNT = 0;
            public const int BLOCK_OFFSET = 1;
            public const int LOB_ID = 2;
        }

        //BLOCK_ADDR INT, BLOCK_COUNT INT, TX_ID BIGINT
        private static String initialiseBlocksSQL =
            "INSERT INTO SYSTEM_LOBS.BLOCKS VALUES(?,?,?)";
        private static String getLobSQL =
            "SELECT * FROM SYSTEM_LOBS.LOB_IDS WHERE LOB_ID = ?";
        private static String getLobPartSQL =
            "SELECT * FROM SYSTEM_LOBS.LOBS WHERE LOB_ID = ? AND BLOCK_OFFSET + BLOCK_COUNT > ? AND BLOCK_OFFSET < ? ORDER BY BLOCK_OFFSET";

        // DELETE_BLOCKS(L_ID BIGINT, B_OFFSET INT, B_COUNT INT, TX_ID BIGINT)
        private static String deleteLobPartSQL =
            "CALL SYSTEM_LOBS.DELETE_BLOCKS(?,?,?,?)";
        private static String createLobSQL =
            "INSERT INTO SYSTEM_LOBS.LOB_IDS VALUES(?, ?, ?, ?)";
        private static String updateLobLengthSQL =
            "UPDATE SYSTEM_LOBS.LOB_IDS SET LOB_LENGTH = ? WHERE LOB_ID = ?";
        private static String createLobPartSQL =
            "CALL SYSTEM_LOBS.ALLOC_BLOCKS(?, ?, ?)";
        private static String divideLobPartSQL =
            "CALL SYSTEM_LOBS.DIVIDE_BLOCK(?, ?)";
        //private static String getSpanningBlockSQL =
        //    "SELECT * FROM SYSTEM_LOBS.LOBS WHERE LOB_ID = ? AND ? > BLOCK_OFFSET AND ? < BLOCK_OFFSET + BLOCK_COUNT";
        private static String updateLobUsageSQL =
            "UPDATE SYSTEM_LOBS.LOB_IDS SET LOB_USAGE_COUNT = ? WHERE LOB_ID = ?";
        private static String getNextLobIdSQL =
            "VALUES NEXT VALUE FOR SYSTEM_LOBS.LOB_ID";
        private static String deleteLobSQL = "CALL SYSTEM_LOBS.DELETE_LOB(?, ?)";

        //    (OUT L_ADDR INT, IN B_COUNT INT, IN B_OFFSET INT, IN L_ID BIGINT, IN L_LENGTH BIGINT)
        public LobManager(Database database)
        {
            this.database = database;
        }

        public void createSchema()
        {

            sysLobSession = database.sessionManager.getSysLobSession();

            Session session = sysLobSession;
            Stream fis = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceFileName);
            StreamReader reader = null;

            try
            {
                reader = new StreamReader(fis);
            }
            catch (Exception )
            { }


            LineGroupReader lg = new LineGroupReader(reader, starters);
            HashMappedList map = lg.getAsMap();

            lg.close();

            String sql = (String)map.get("/*lob_schema_definition*/");
            Statement statement = session.compileStatement(sql);
            Result result = statement.execute(session);
            QNameManager.QName name = database.schemaManager.getSchemaHsqlName("SYSTEM_LOBS");
            name.owner = SqlInvariants.LOBS_SCHEMA_QNAME.owner;

            Table table = database.schemaManager.getTable(session, "BLOCKS",
                "SYSTEM_LOBS");

            _getLob = session.compileStatement(getLobSQL);
            getLobPart = session.compileStatement(getLobPartSQL);
            createLob = session.compileStatement(createLobSQL);
            createLobPart = session.compileStatement(createLobPartSQL);
            divideLobPart = session.compileStatement(divideLobPartSQL);
            _deleteLob = session.compileStatement(deleteLobSQL);
            deleteLobPart = session.compileStatement(deleteLobPartSQL);
            setLobLength = session.compileStatement(updateLobLengthSQL);
            setLobUsage = session.compileStatement(updateLobUsageSQL);
            getNextLobId = session.compileStatement(getNextLobIdSQL);
        }

        public void initialiseLobSpace()
        {

            Statement statement =
                sysLobSession.compileStatement(initialiseBlocksSQL);
            Object[] args = new Object[3];

            args[0] = (int)(0);
            args[1] = (int)(totalBlockLimitCount);
            args[2] = (long)(0);

            sysLobSession.executeCompiledStatement(statement, args);
        }

        public void open()
        {

            if (database.getType() == DatabaseURL.S_FILE)
            {
                lobStore = new LobStoreRAFile(database, lobBlockSize);
            }
            else
            {
                lobStore = new LobStoreMem(lobBlockSize);
            }
        }

        public void close() { }

        //
        private long getNewLobID(Session session)
        {

            Result result = getNextLobId.execute(session);

            if (result.isError())
            {
                return 0;
            }

            RowSetNavigator navigator = result.getNavigator();
            bool next = navigator.next();

            if (!next)
            {
                navigator.close();

                return 0;
            }

            Object[] data = navigator.getCurrent();

            return Convert.ToInt64(data[0]);
        }

        private Object[] getLobHeader(Session session, long lobID)
        {

            ResultMetaData meta = _getLob.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (lobID);

            session.sessionContext.pushDynamicArguments(paras);

            Result result = _getLob.execute(session);

            session.sessionContext.popDynamicArguments();

            if (result.isError())
            {
                return null;
            }

            RowSetNavigator navigator = result.getNavigator();
            bool next = navigator.next();

            if (!next)
            {
                navigator.close();

                return null;
            }

            Object[] data = navigator.getCurrent();

            return data;
        }

        public BlobData getBlob(Session session, long lobID)
        {

            Object[] data = getLobHeader(session, lobID);

            if (data == null)
            {
                return null;
            }

            BlobData blob = new BlobDataID(lobID);

            return blob;
        }

        public ClobData getClob(Session session, long lobID)
        {

            Object[] data = getLobHeader(session, lobID);

            if (data == null)
            {
                return null;
            }

            ClobData clob = new ClobDataID(lobID);

            return clob;
        }

        public long createBlob(long length)
        {

            long lobID = getNewLobID(sysLobSession);
            ResultMetaData meta = createLob.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (long)(lobID);
            paras[1] = (long)(length);
            paras[2] = (long)(1);
            paras[3] = (int)(Types.SQL_BLOB);

            Result result = sysLobSession.executeCompiledStatement(createLob,
                paras);

            return lobID;
        }

        public long createClob(long length)
        {

            long lobID = getNewLobID(sysLobSession);
            ResultMetaData meta = createLob.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (long)(lobID);
            paras[1] = (long)(length);
            paras[2] = (long)(1);
            paras[3] = (int)(Types.SQL_CLOB);

            Result result = sysLobSession.executeCompiledStatement(createLob,
                paras);

            return lobID;
        }

        public Result deleteLob(long lobID)
        {

            Session session = this.sysLobSession;
            ResultMetaData meta = _deleteLob.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (long)(lobID);
            paras[1] = (long)(0);

            Result result = session.executeCompiledStatement(_deleteLob, paras);

            return result;
        }

        public Result getLength(Session session, long lobID)
        {

            try
            {
                long length = getLengthValue(session, lobID);

                return ResultLob.newLobSetResponse(lobID, length);
            }
            catch (CoreException e)
            {
                return Result.newErrorResult(e);
            }
        }

        public long getLengthValue(Session session, long lobID)
        {

            Object[] data = getLobHeader(session, lobID);

            if (data == null)
            {
                throw Error.error(ErrorCode.X_0F502);
            }

            long length = Convert.ToInt64(data[1]);

            return length;
        }

        /**
         * Used for SUBSTRING
         */
        public Result getLob(Session session, long lobID, long offset,
                             long length)
        {
            throw Error.runtimeError(ErrorCode.U_S0500, "LobManager");
        }

        public Result createDuplicateLob(Session session, long lobID)
        {

            Object[] data = getLobHeader(session, lobID);

            if (data == null)
            {
                return Result.newErrorResult(Error.error(ErrorCode.X_0F502));
            }

            long newLobID = getNewLobID(session);
            Object[] paras = new Object[data.Length];

            paras[0] = (long)(newLobID);
            paras[1] = data[1];
            paras[2] = data[2];
            paras[3] = data[3];

            Result result = session.executeCompiledStatement(createLob, paras);

            if (result.isError())
            {
                return result;
            }

            long length = Convert.ToInt64(data[1]);
            long byteLength = length;
            int lobType = Convert.ToInt32(data[1]);

            if (lobType == Types.SQL_CLOB)
            {
                byteLength *= 2;
            }

            int newBlockCount = (int)byteLength / lobBlockSize;

            if (byteLength % lobBlockSize != 0)
            {
                newBlockCount++;
            }

            createBlockAddresses(session, newLobID, 0, newBlockCount);

            // copy the contents
            int[][] sourceBlocks = getBlockAddresses(session, lobID, 0,
                int.MaxValue);
            int[][] targetBlocks = getBlockAddresses(session, newLobID, 0,
                int.MaxValue);

            try
            {
                copyBlockSet(sourceBlocks, targetBlocks);
            }
            catch (CoreException e)
            {
                return Result.newErrorResult(e);
            }

            return ResultLob.newLobSetResponse(newLobID, length);
        }

        private void copyBlockSet(int[][] source, int[][] target)
        {

            int sourceIndex = 0;
            int targetIndex = 0;

            while (true)
            {
                int sourceOffset = source[sourceIndex][LOBS.BLOCK_OFFSET]
                                   + sourceIndex;
                int targetOffset = target[targetIndex][LOBS.BLOCK_OFFSET]
                                   + targetIndex;
                byte[] bytes = lobStore.getBlockBytes(sourceOffset, 1);

                lobStore.setBlockBytes(bytes, targetOffset, 1);

                sourceOffset++;
                targetOffset++;

                if (sourceOffset == source[sourceIndex][LOBS.BLOCK_COUNT])
                {
                    sourceOffset = 0;

                    sourceIndex++;
                }

                if (targetOffset == target[sourceIndex][LOBS.BLOCK_COUNT])
                {
                    targetOffset = 0;

                    targetIndex++;
                }

                if (sourceIndex == source.Length)
                {
                    break;
                }
            }
        }

        public Result getChars(Session session, long lobID, long offset,
                               int length)
        {

            Result result = getBytes(session, lobID, offset * 2, length * 2);

            if (result.isError())
            {
                return result;
            }

            byte[] bytes = ((ResultLob)result).getByteArray();
            ByteArrayInputStream be = new ByteArrayInputStream(bytes);
            char[] chars = new char[bytes.Length / 2];

            try
            {
                for (int i = 0; i < chars.Length; i++)
                {
                    chars[i] = be.readChar();
                }
            }
            catch (Exception e)
            {
                return Result.newErrorResult(e);
            }

            return ResultLob.newLobGetCharsResponse(lobID, offset, chars);
        }

        public Result getBytes(Session session, long lobID, long offset,
                               int length)
        {

            int blockOffset = (int)(offset / lobBlockSize);
            int byteBlockOffset = (int)(offset % lobBlockSize);
            int blockLimit = (int)((offset + length) / lobBlockSize);
            int byteLimitOffset = (int)((offset + length) % lobBlockSize);

            if (byteLimitOffset == 0)
            {
                byteLimitOffset = lobBlockSize;
            }
            else
            {
                blockLimit++;
            }

            int dataBytesPosition = 0;
            byte[] dataBytes = new byte[length];
            int[][] blockAddresses = getBlockAddresses(session, lobID,
                blockOffset, blockLimit);

            if (blockAddresses.Length == 0)
            {
                return Result.newErrorResult(Error.error(ErrorCode.X_0F502));
            }

            //
            int i = 0;
            int blockCount = blockAddresses[i][1] +
                             blockAddresses[i][2] - blockOffset;

            if (blockAddresses[i][1] + blockAddresses[i][2] > blockLimit)
            {
                blockCount -= (blockAddresses[i][1] + blockAddresses[i][2]
                               - blockLimit);
            }

            byte[] bytes;

            try
            {
                bytes = lobStore.getBlockBytes(blockAddresses[i][0] + blockOffset,
                                               blockCount);
            }
            catch (CoreException e)
            {
                return Result.newErrorResult(e);
            }

            int subLength = lobBlockSize * blockCount - byteBlockOffset;

            if (subLength > length)
            {
                subLength = length;
            }

            Array.Copy(bytes, byteBlockOffset, dataBytes, dataBytesPosition,
                             subLength);

            dataBytesPosition += subLength;

            i++;

            for (; i < blockAddresses.Length && dataBytesPosition < length; i++)
            {
                blockCount = blockAddresses[i][1];

                if (blockAddresses[i][1] + blockAddresses[i][2] > blockLimit)
                {
                    blockCount -= (blockAddresses[i][1] + blockAddresses[i][2]
                                   - blockLimit);
                }

                try
                {
                    bytes = lobStore.getBlockBytes(blockAddresses[i][0],
                                                   blockCount);
                }
                catch (CoreException e)
                {
                    return Result.newErrorResult(e);
                }

                subLength = lobBlockSize * blockCount;

                if (subLength > length - dataBytesPosition)
                {
                    subLength = length - dataBytesPosition;
                }

                Array.Copy(bytes, 0, dataBytes, dataBytesPosition,
                                 subLength);

                dataBytesPosition += subLength;
            }

            return ResultLob.newLobGetBytesResponse(lobID, offset, dataBytes);
        }

        public Result setBytesBA(Session session, long lobID, byte[] dataBytes,
                                 long offset, int length)
        {

            Object[] data = getLobHeader(session, lobID);

            if (data == null)
            {
                return Result.newErrorResult(Error.error(ErrorCode.X_0F502));
            }

            long oldLength = Convert.ToInt64(data[1]);
            int blockOffset = (int)(offset / lobBlockSize);
            int byteBlockOffset = (int)(offset % lobBlockSize);
            int blockLimit = (int)((offset + length) / lobBlockSize);
            int byteLimitOffset = (int)((offset + length) % lobBlockSize);

            if (byteLimitOffset == 0)
            {
                byteLimitOffset = lobBlockSize;
            }
            else
            {
                blockLimit++;
            }

            int[][] blockAddresses = getBlockAddresses(session, lobID,
                blockOffset, blockLimit);
            byte[] newBytes = new byte[(blockLimit - blockOffset) * lobBlockSize];

            if (blockAddresses.Length > 0)
            {
                int blockAddress = blockAddresses[0][0]
                                   + (blockOffset - blockAddresses[0][2]);

                try
                {
                    byte[] block = lobStore.getBlockBytes(blockAddress, 1);

                    Array.Copy(block, 0, newBytes, 0, lobBlockSize);

                    if (blockAddresses.Length > 1)
                    {
                        blockAddress =
                            blockAddresses[blockAddresses.Length - 1][0]
                            + (blockLimit
                               - blockAddresses[blockAddresses.Length - 1][2] - 1);
                        block = lobStore.getBlockBytes(blockAddress, 1);

                        Array.Copy(block, 0, newBytes,
                                         blockLimit - blockOffset - 1,
                                         lobBlockSize);
                    }
                    else if (blockLimit - blockOffset > 1)
                    {
                        blockAddress = blockAddresses[0][0]
                                       + (blockLimit - blockAddresses[0][2] - 1);
                        block = lobStore.getBlockBytes(blockAddress, 1);

                        Array.Copy(block, 0, newBytes,
                                         (blockLimit - blockOffset - 1)
                                         * lobBlockSize, lobBlockSize);
                    }
                }
                catch (CoreException e)
                {
                    return Result.newErrorResult(e);
                }

                // should turn into SP
                divideBlockAddresses(session, lobID, blockOffset);
                divideBlockAddresses(session, lobID, blockLimit);
                deleteBlockAddresses(session, lobID, blockOffset, blockLimit);
            }

            createBlockAddresses(session, lobID, blockOffset,
                                 blockLimit - blockOffset);
            Array.Copy(dataBytes, 0, newBytes, byteBlockOffset, length);

            blockAddresses = getBlockAddresses(session, lobID, blockOffset,
                                               blockLimit);

            //
            try
            {
                for (int i = 0; i < blockAddresses.Length; i++)
                {
                    lobStore.setBlockBytes(newBytes, blockAddresses[i][0],
                                           blockAddresses[i][1]);
                }
            }
            catch (CoreException e)
            {
                return Result.newErrorResult(e);
            }

            if (offset + length > oldLength)
            {
                oldLength = offset + length;

                setLength(session, lobID, oldLength);
            }

            return ResultLob.newLobSetResponse(lobID, 0);
        }

        private Result setBytesIS(Session session, long lobID,
                                  Stream inputStream, long length)
        {

            int blockLimit = (int)(length / lobBlockSize);
            int byteLimitOffset = (int)(length % lobBlockSize);

            if (byteLimitOffset == 0)
            {
                byteLimitOffset = lobBlockSize;
            }
            else
            {
                blockLimit++;
            }

            createBlockAddresses(session, lobID, 0, blockLimit);

            int[][] blockAddresses = getBlockAddresses(session, lobID, 0,
                blockLimit);
            byte[] dataBytes = new byte[lobBlockSize];

            for (int i = 0; i < blockAddresses.Length; i++)
            {
                for (int j = 0; j < blockAddresses[i][1]; j++)
                {
                    int localLength = lobBlockSize;

                    if (i == blockAddresses.Length - 1
                            && j == blockAddresses[i][1] - 1)
                    {
                        localLength = byteLimitOffset;

                        // todo -- use block op
                        for (int k = localLength; k < lobBlockSize; k++)
                        {
                            dataBytes[k] = 0;
                        }
                    }

                    try
                    {
                        int count = 0;

                        while (localLength > 0)
                        {
                            int read = inputStream.Read(dataBytes, count,
                                                        localLength);

                            if (read == -1)
                            {
                                return Result.newErrorResult(new EndOfStreamException());
                            }

                            localLength -= read;
                            count += read;
                        }

                        // read more
                    }
                    catch (IOException e)
                    {

                        // deallocate
                        return Result.newErrorResult(e);
                    }

                    try
                    {
                        lobStore.setBlockBytes(dataBytes,
                                               blockAddresses[i][0] + j, 1);
                    }
                    catch (CoreException e)
                    {
                        return Result.newErrorResult(e);
                    }
                }
            }

            return ResultLob.newLobSetResponse(lobID, 0);
        }

        public Result setBytes(Session session, long lobID, byte[] dataBytes,
                               long offset)
        {

            if (dataBytes.Length == 0)
            {
                return ResultLob.newLobSetResponse(lobID, 0);
            }

            Object[] data = getLobHeader(session, lobID);

            if (data == null)
            {
                return Result.newErrorResult(Error.error(ErrorCode.X_0F502));
            }

            long length = Convert.ToInt64(data[1]);
            Result result = setBytesBA(session, lobID, dataBytes, offset,
                                       dataBytes.Length);

            if (offset + dataBytes.Length > length)
            {
                setLength(session, lobID, offset + dataBytes.Length);
            }

            return result;
        }

        public Result setBytesForNewBlob(long lobID, Stream inputStream,
                                         long length)
        {

            Session session = sysLobSession;

            if (length == 0)
            {
                return ResultLob.newLobSetResponse(lobID, 0);
            }

            Result result = setBytesIS(session, lobID, inputStream, length);

            return result;
        }

        public Result setChars(Session session, long lobID, long offset,
                               char[] chars)
        {

            if (chars.Length == 0)
            {
                return ResultLob.newLobSetResponse(lobID, 0);
            }

            Object[] data = getLobHeader(session, lobID);

            if (data == null)
            {
                return Result.newErrorResult(Error.error(ErrorCode.X_0F502));
            }

            long length = Convert.ToInt64(data[1]);
            ByteArrayOutputStream os =
                new ByteArrayOutputStream(chars.Length * 2);

            os.write(chars, 0, chars.Length);

            Result result = setBytesBA(session, lobID, os.getBuffer(), offset * 2,
                                       os.getBuffer().Length);

            if (result.isError())
            {
                return result;
            }

            if (offset + chars.Length > length)
            {
                result = setLength(session, lobID, offset + chars.Length);

                if (result.isError())
                {
                    return result;
                }
            }

            return ResultLob.newLobSetResponse(lobID, 0);
        }

        public Result setCharsForNewClob(long lobID, Stream inputStream,
                                         long length)
        {

            Session session = sysLobSession;

            if (length == 0)
            {
                return ResultLob.newLobSetResponse(lobID, 0);
            }

            Result result = setBytesIS(session, lobID, inputStream, length * 2);

            if (result.isError())
            {
                return result;
            }

            return ResultLob.newLobSetResponse(lobID, 0);
        }

        public Result truncate(Session session, long lobID, long offset)
        {

            Object[] data = getLobHeader(session, lobID);

            if (data == null)
            {
                return Result.newErrorResult(Error.error(ErrorCode.X_0F502));
            }

            /** @todo 1.9.0 - double offset for clob */
            long length = Convert.ToInt64(data[1]);
            int blockOffset = (int)(offset / lobBlockSize);
            int blockLimit = (int)((offset + length) / lobBlockSize);
            int byteLimitOffset = (int)((offset + length) % lobBlockSize);

            if (byteLimitOffset != 0)
            {
                blockLimit++;
            }

            ResultMetaData meta = deleteLobPart.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (lobID);
            paras[1] = (blockOffset);
            paras[2] = (blockLimit);
            paras[3] = (session.getTransactionTimestamp());

            Result result = session.executeCompiledStatement(deleteLobPart,
                paras);

            setLength(session, lobID, offset);

            return ResultLob.newLobTruncateResponse(lobID);
        }

        public Result setLength(Session session, long lobID, long length)
        {

            ResultMetaData meta = setLobLength.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (length);
            paras[1] = (lobID);

            Result result = session.executeCompiledStatement(setLobLength, paras);

            return result;
        }

        public Result adjustUsageCount(long lobID, int delta)
        {

            Object[] data = getLobHeader(sysLobSession, lobID);
            int count = Convert.ToInt32(data[2]);

            if (count + delta == 0)
            {
                return deleteLob(lobID);
            }

            ResultMetaData meta = setLobUsage.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (long)(count + delta);
            paras[1] = (lobID);

            Result result = sysLobSession.executeCompiledStatement(setLobLength,
                paras);

            return result;
        }

        int[][] getBlockAddresses(Session session, long lobID, int offset,
                                  int limit) {

        ResultMetaData meta     = getLobPart.getParametersMetaData();
        Object[]         paras = new Object[meta.getColumnCount()];

        paras[0] = (lobID);
        paras[1] = (offset);
        paras[2] = (limit);

        session.sessionContext.pushDynamicArguments(paras);

        Result result = getLobPart.execute(session);

        session.sessionContext.popDynamicArguments();

        RowSetNavigator navigator = result.getNavigator();
        int             size      = navigator.getSize();
        int[][]         blocks    = new int[size][];

        for (int i = 0; i < size; i++) {
            
            navigator.absolute(i);

            Object[] data = navigator.getCurrent();
            blocks[i] = new int[3];
            blocks[i][0] = Convert.ToInt32 (data[LOBS.BLOCK_ADDR]);
            blocks[i][1] =Convert.ToInt32 ( data[LOBS.BLOCK_COUNT]);
            blocks[i][2] =Convert.ToInt32 ( data[LOBS.BLOCK_OFFSET]);
        }

        navigator.close();

        return blocks;
    }

        void deleteBlockAddresses(Session session, long lobID, int offset,
                                  int count)
        {

            ResultMetaData meta = deleteLobPart.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (lobID);
            paras[1] = (offset);
            paras[2] = (count);

            Result result = session.executeCompiledStatement(deleteLobPart,
                paras);
        }

        void divideBlockAddresses(Session session, long lobID, int offset)
        {

            ResultMetaData meta = divideLobPart.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[0] = (lobID);
            paras[1] = (offset);

            Result result = session.executeCompiledStatement(divideLobPart,
                paras);
        }

        void createBlockAddresses(Session session, long lobID, int offset,
                                  int count)
        {

            ResultMetaData meta = createLobPart.getParametersMetaData();
            Object[] paras = new Object[meta.getColumnCount()];

            paras[ALLOC_BLOCKS.BLOCK_COUNT] = (count);
            paras[ALLOC_BLOCKS.BLOCK_OFFSET] = (offset);
            paras[ALLOC_BLOCKS.LOB_ID] = (lobID);

            Result result = session.executeCompiledStatement(createLobPart,
                paras);
        }
    }
}
